package ua.lviv.franko.solvers.stressanalysis.twoDimention;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.jzy3d.maths.Range;
import org.jzy3d.plot3d.builder.Mapper;

import ua.lviv.franko.integration.IFunction2D;
import ua.lviv.franko.matrix.RowType;
import ua.lviv.franko.matrix.hash.lu.HashMatrixLUStyle;
import ua.lviv.franko.matrix.hash.lu.LUFactorization;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.CubicSpline;
import ua.lviv.franko.mlsFunctions.twoDimension.weightFunctions.SquareSupport;
import ua.lviv.franko.solvers.stressanalysis.twoDimention.fem.AbstractStressElement;
import ua.lviv.franko.solvers.stressanalysis.twoDimention.fem.LinearRectangularElement;
import ua.lviv.franko.solvers.stressanalysis.twoDimention.fem.enriched.BilinearIsoparamerticEnrichedQuadrilateral;
import ua.lviv.franko.solvers.stressanalysis.twoDimention.fem.enriched.Enriched;
import ua.lviv.franko.triangulation.SaveStructures.Node;
import ua.lviv.franko.triangulation.SaveStructures.Rectangle;
import ua.lviv.franko.visualisation.Visualise3D;

public class EnrichedIsoparametricSolver {
	private static final int					NUMBER_OF_UNKNOWNS	= 2;
	private static final double					EPS					= 0.000000000001;
	protected ArrayList<AbstractStressElement>	elementsList;
	protected HashMatrixLUStyle						mainMatrix;
	protected List<Double>						b;

	protected int								lastNodeNumber		= 0;

	public EnrichedIsoparametricSolver(int num,ArrayList<AbstractStressElement> elementsList) {
		this.elementsList = elementsList;
		lastNodeNumber = num;// TODO !!!!!!!!!!!! fix this
	}

	public void generate() {
		// int N = elementsList.get(elementsList.size()-1).lastNumber()+1;
		int N = lastNodeNumber;
		mainMatrix = new HashMatrixLUStyle(RowType.ARRAY_TYPE, N * NUMBER_OF_UNKNOWNS);
		b = new ArrayList<Double>(N * NUMBER_OF_UNKNOWNS);
		for (int i = 0; i < N*NUMBER_OF_UNKNOWNS; i++) {
			b.add(0.0);
		}

		for (Iterator<AbstractStressElement> iterator = elementsList.iterator(); iterator.hasNext();) {
			AbstractStressElement element = iterator.next();
			ElementMatrixForStress data = element.generateAll();
			List<Integer> pos = data.getNodeList();
			for (int i = 0; i < pos.size(); i++) {
				for (int j = 0; j < pos.size(); j++) {
					mainMatrix.setValue(pos.get(i) * NUMBER_OF_UNKNOWNS, pos.get(j) * NUMBER_OF_UNKNOWNS,
							mainMatrix.getValue(pos.get(i) * NUMBER_OF_UNKNOWNS, pos.get(j) * NUMBER_OF_UNKNOWNS) + data.matrix[NUMBER_OF_UNKNOWNS * i][NUMBER_OF_UNKNOWNS * j]);

					mainMatrix.setValue(pos.get(i) * NUMBER_OF_UNKNOWNS, pos.get(j) * NUMBER_OF_UNKNOWNS + 1,
							mainMatrix.getValue(pos.get(i) * NUMBER_OF_UNKNOWNS, pos.get(j) * NUMBER_OF_UNKNOWNS + 1)
									+ data.matrix[NUMBER_OF_UNKNOWNS * i][NUMBER_OF_UNKNOWNS * j + 1]);
					mainMatrix.setValue(pos.get(i) * NUMBER_OF_UNKNOWNS + 1, pos.get(j) * NUMBER_OF_UNKNOWNS,
							mainMatrix.getValue(pos.get(i) * NUMBER_OF_UNKNOWNS + 1, pos.get(j) * NUMBER_OF_UNKNOWNS)
									+ data.matrix[NUMBER_OF_UNKNOWNS * i + 1][NUMBER_OF_UNKNOWNS * j]);
					mainMatrix.setValue(pos.get(i) * NUMBER_OF_UNKNOWNS + 1, pos.get(j) * NUMBER_OF_UNKNOWNS + 1,
							mainMatrix.getValue(pos.get(i) * NUMBER_OF_UNKNOWNS + 1, pos.get(j) * NUMBER_OF_UNKNOWNS + 1)
									+ data.matrix[NUMBER_OF_UNKNOWNS * i + 1][NUMBER_OF_UNKNOWNS * j + 1]);
				}
				b.set(pos.get(i) * NUMBER_OF_UNKNOWNS, b.get(pos.get(i) * NUMBER_OF_UNKNOWNS) + data.vector[i * NUMBER_OF_UNKNOWNS]);
				b.set(pos.get(i) * NUMBER_OF_UNKNOWNS + 1, b.get(pos.get(i) * NUMBER_OF_UNKNOWNS + 1) + data.vector[i * NUMBER_OF_UNKNOWNS + 1]);
			}
			if (element instanceof Enriched) {
				Enriched enricedElement = (Enriched) element;
				int startNumber = lastNodeNumber;
				lastNodeNumber = enricedElement.generateEnrichmentMatrixVector(lastNodeNumber);
				double[][] tmpMatrix = enricedElement.getLocalMatrix();
				double[] tmpVector = enricedElement.getLocalBVector();

				mainMatrix.addRows(tmpVector.length);

				for (int i = 0; i < tmpMatrix.length; i++) {
					b.add(i + startNumber*NUMBER_OF_UNKNOWNS, tmpVector[i]);
					for (int j = 0; j < tmpVector.length; j++) {
						mainMatrix.setValue(i + startNumber*NUMBER_OF_UNKNOWNS, j + startNumber*NUMBER_OF_UNKNOWNS, tmpMatrix[i][j]);
					}
				}

			}
		}
		System.out.println("-----------------");
		for (int i = 0; i < b.size(); i++) {
			for (int j = 0; j < b.size(); j++) {
				System.out.print(mainMatrix.getValue(i, j) + "\t");
			}
			System.out.print("\n");
			// System.out.println(b[i]);
		}
		System.out.println("-----------------");

	}

	public double[] solve() {
		LUFactorization lu = new LUFactorization(this.mainMatrix);
		double[] bToSolve = new double[b.size()];
		for (int i = 0; i < bToSolve.length; i++) {
			bToSolve[i] = b.get(i);
		}
		return lu.calculate(bToSolve);
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// ArrayList<Node> nodeList = new ArrayList<Node>();
		double ax = -0.5, bx = 0.5, ay = -0.5, by = 0.5;

		int n = 2, m = 2;

		double hx = (bx - ax) / (double) n, hy = (by - ay) / (double) m;

		// вузли+функції:
		// for (int i = 0; i <= m; i++) {
		// for (int j = 0; j <= n; j++) {
		// Node p = new Node(ax + j * hx, ay + i * hy, false, i * n + j);
		// System.out.println(p.x + "\t" + p.y);
		// // if ((i==0)||(j==0)) {
		// // p.IsOnBoundery=true;
		// // }
		// // nodeList.add(p);
		// }
		// }
		double k1 = 1;
		double k2 = 0.5;
		double k3 = 0.2;

		IFunction2D f1 = new IFunction2D() {

			@Override
			public double calculate(double x, double y) {

				return 0;
			}

			@Override
			public double calculateDerivatyX(double x, double y) {

				return 0;
			}

			@Override
			public double calculateDerivatyY(double x, double y) {

				return 0;
			}
		};
		IFunction2D f2 = new IFunction2D() {

			@Override
			public double calculate(double x, double y) {

				return 0;
			}

			@Override
			public double calculateDerivatyX(double x, double y) {

				return 0;
			}

			@Override
			public double calculateDerivatyY(double x, double y) {

				return 0;
			}
		};
		ArrayList<AbstractStressElement> elems = new ArrayList<AbstractStressElement>();

		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				Rectangle r = new Rectangle();
				r.A = new Node(ax + j * hx, ay, false, i * (n + 1) + j);
				r.B = new Node(ax + (j + 1) * hx, ay, false, i * (n + 1) + j + 1);
				r.C = new Node(ax + (j + 1) * hx, ay + hy, false, (i + 1) * (n + 1) + j + 1);
				r.D = new Node(ax + j * hx, ay + hy, false, (i + 1) * (n + 1) + j);

				LinearRectangularElement elem = new LinearRectangularElement(r.A, r.B, r.C, r.D, k1, k2, k3, f1, f2);
				elems.add(elem);
			}
			ay += hy;
		}
		elems.set(0, new BilinearIsoparamerticEnrichedQuadrilateral(k1, k2, k3, f1, f2, new Node(ax, ay, false, 1), new Node(ax + hx, ay, false, 0), new Node(ax + hx, ay + hy,
				false, (n + 1) + 1), new Node(ax, ay + hx, false, (n + 1)), BilinearIsoparamerticEnrichedQuadrilateral.generateParticlesOnlyInsideQuadElement(3, 3, 3.1), 2,
				new SquareSupport(new CubicSpline())));
		EnrichedIsoparametricSolver s = new EnrichedIsoparametricSolver((n + 1) * (m + 1),elems);
		s.generate();
		double[] res = s.solve();
		for (int k = 0; k < res.length; k++) {
			System.out.println(res[k]);
		}
		final ArrayList<AbstractStressElement> el = elems;
		final double[] vals = res;

		ay = -0.5;

		Visualise3D.show(new Mapper() {
			@Override
			public double f(double x, double y) {
				double val = 0.0;
				int i = 0;
				while (val == 0.0 && i < el.size()) {
					val += el.get(i).calculateU(x, y, vals);
					i++;
				}

				// return ff.calculateDerivatyY(x, y);//(x, y);
				return val;
			}
		}, new Range(ax, bx), new Range(ay, by));

		Visualise3D.show(new Mapper() {
			@Override
			public double f(double x, double y) {
				double val = 0.0;
				int i = 0;
				while (val == 0.0 && i < el.size()) {
					val += el.get(i).calculateV(x, y, vals);
					i++;
				}

				// return ff.calculateDerivatyY(x, y);//(x, y);
				return val;
			}
		}, new Range(ax, bx), new Range(ay, by));

	}
}
